#include "MainSceneWidget.h"
#include <qgl.h>
#include <gl/glu.h>
#include <QMouseEvent>
#include <fstream>

std::vector< std::vector< glm::ivec3 > > triangleList(256);
void genMarchingCubeTable ();
int c = 0;
float vertices[12][3] = {
	{ -1, -1, 0 }, { -1, 1, 0 }, { 1, -1, 0 }, { 1, 1, 0 },
	{ -1, 0, -1 }, { -1, 0, 1 }, { 1, 0, -1 }, { 1, 0, 1 },
	{ 0, -1, -1 }, { 0, -1, 1 }, { 0, 1, -1 }, { 0, 1, 1 }
};

MainSceneWidget::MainSceneWidget (QWidget *parent) :
		QGLWidget (QGLFormat (
		QGL::DoubleBuffer | QGL::DepthBuffer | QGL::Rgba), parent), _cameraInterface (g_camera)
{
}

MainSceneWidget::~MainSceneWidget ()
{

}

void MainSceneWidget::initializeGL ()
{
	glEnable (GL_DEPTH_TEST);

	g_camera.setDistance (20);
	g_camera.setViewpoint (glm::vec3 (0, 0, 0));
	g_camera.setCameraAngle (45, -45);
	genMarchingCubeTable ();
}

void MainSceneWidget::paintGL ()
{

	// Clear the OpenGL buffers
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glPushAttrib (GL_ENABLE_BIT);

	// Setup the OpenGL viewpoint
	glViewport (0, 0, width (), height ());
	glMatrixMode (GL_PROJECTION);
	glPushMatrix ();
	glLoadIdentity ();
	gluPerspective (45.0, (double)width () / height (), 1.0, 5000);

	glMatrixMode (GL_MODELVIEW);
	renderText (20, 20, QString ("%1").arg (c));
	glPushMatrix ();
	g_camera.setSight ();

	_drawCoord ();
	_setLight ();

	glPopMatrix ();
	glMatrixMode (GL_PROJECTION);
	glPopMatrix ();

	glPopAttrib ();

	update ();
}


void MainSceneWidget::_setLight ()
{
	glEnable (GL_NORMALIZE);
	glEnable (GL_LIGHTING);
	glEnable (GL_LIGHT0);
 	float lp[] = { 1.0f, 1.0f, 1.0f, 0.0f };
	float lc[] = { 1.0f, 1.0f, 1.0f, 1.0f };
 	glLightfv (GL_LIGHT0, GL_POSITION, lp);
	glLightfv (GL_LIGHT0, GL_AMBIENT, lc);
	glLightfv (GL_LIGHT0, GL_DIFFUSE, lc);
	glLightModeli (GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
}

void MainSceneWidget::mousePressEvent (QMouseEvent * e)
{
	if (e->button () & Qt::LeftButton)
		_cameraInterface.mouseDown (CameraInterface::B_LEFT,
				e->pos ().x (), e->pos ().y ());
	if (e->button () & Qt::MidButton)
		_cameraInterface.mouseDown (CameraInterface::B_MIDDLE,
				e->pos ().x (), e->pos ().y ());
	if (e->button () & Qt::RightButton)
		_cameraInterface.mouseDown (CameraInterface::B_RIGHT,
				e->pos ().x (), e->pos ().y ());
}

void MainSceneWidget::mouseMoveEvent (QMouseEvent * e)
{
	_cameraInterface.mouseDrag (e->pos ().x (), e->pos ().y ());
}

void MainSceneWidget::mouseReleaseEvent (QMouseEvent * e)
{
	if (e->button () & Qt::LeftButton)
		_cameraInterface.mouseUp (CameraInterface::B_LEFT,
				e->pos ().x (), e->pos ().y ());
	if (e->button () & Qt::MidButton)
		_cameraInterface.mouseUp (CameraInterface::B_MIDDLE,
				e->pos ().x (), e->pos ().y ());
	if (e->button () & Qt::RightButton)
		_cameraInterface.mouseUp (CameraInterface::B_RIGHT,
				e->pos ().x (), e->pos ().y ());
}

void MainSceneWidget::wheelEvent (QWheelEvent * e)
{
	_cameraInterface.mouseWheel (e->delta () / 120);
}

void MainSceneWidget::keyPressEvent (QKeyEvent * e)
{
	_cameraInterface.keyDown (e->key () - 'A' + 'a');
	switch (e->key ()) {
	case Qt::Key_Escape:
		close ();
	case Qt::Key_Left:
		if (c > 0) --c; break;
	case Qt::Key_Right:
		if (c < 255) ++c; break;
	}
}

void MainSceneWidget::keyReleaseEvent (QKeyEvent * e)
{
	
}

void MainSceneWidget::_drawCoord ()
{
	glBegin (GL_LINES);
	glColor3f (1, 0, 0); glVertex3f (0, 0, 0); glVertex3f (8, 0, 0);
	glColor3f (0, 1, 0); glVertex3f (0, 0, 0); glVertex3f (0, 6, 0);
	glColor3f (0, 0, 1); glVertex3f (0, 0, 0); glVertex3f (0, 0, 10);
	glEnd ();

	glColor3f (1, 1, 1);
	glPushMatrix ();
	glScalef (15.0, 15.0, 15.0);

	glBegin (GL_LINES);
	glVertex3f (-1, -1, -1); glVertex3f (1, -1, -1);
	glVertex3f (1, -1, -1); glVertex3f (1, 1, -1);
	glVertex3f (1, 1, -1); glVertex3f (-1, 1, -1);
	glVertex3f (-1, 1, -1); glVertex3f (-1, -1, -1);
	glVertex3f (-1, -1, 1); glVertex3f (1, -1, 1);
	glVertex3f (1, -1, 1); glVertex3f (1, 1, 1);
	glVertex3f (1, 1, 1); glVertex3f (-1, 1, 1);
	glVertex3f (-1, 1, 1); glVertex3f (-1, -1, 1);
	glVertex3f (-1, -1, -1); glVertex3f (-1, -1, 1);
	glVertex3f (1, -1, -1); glVertex3f (1, -1, 1);
	glVertex3f (1, 1, -1); glVertex3f (1, 1, 1);
	glVertex3f (-1, 1, -1); glVertex3f (-1, 1, 1);
	glEnd ();
// 	glBegin (GL_LINE_LOOP);
// 	glVertex3f (-1, 0, 1); glVertex3f (-1, 0, -1); glVertex3f (0, 1, -1);
// 	glEnd ();
// 	glBegin (GL_LINE_LOOP);
// 	glVertex3f (0, 1, -1); glVertex3f (1, 1, 0); glVertex3f (1, -1, 0);
// 	glEnd ();
// 	glBegin (GL_LINE_LOOP);
// 	glVertex3f (1, -1, 0); glVertex3f (0, 1, -1); glVertex3f (-1, 0, -1);
// 	glEnd ();
// 	glBegin (GL_LINE_LOOP);
// 	glVertex3f (1, -1, 0); glVertex3f (-1, 0, -1); glVertex3f (-1, 0, 1);
// 	glEnd ();
// 	glBegin (GL_LINE_LOOP);
// 	glVertex3f (1, -1, 0); glVertex3f (-1, 0, 1); glVertex3f (0, -1, 1);
// 	glEnd ();
	
	for (int i = 0; i < triangleList[c].size (); ++i) {
		glBegin (GL_LINE_LOOP);
		glVertex3fv (vertices[triangleList[c][i][0]]);
		glVertex3fv (vertices[triangleList[c][i][1]]);
		glVertex3fv (vertices[triangleList[c][i][2]]);
		glEnd ();
	}
	glPopMatrix ();
}

glm::ivec3 edgesOfVertexArr[8] = {
	glm::ivec3 (0, 4, 8), glm::ivec3 (0, 5, 9), glm::ivec3 (1, 4, 10), glm::ivec3 (1, 5, 11), 
	glm::ivec3 (2, 6, 8), glm::ivec3 (2, 7, 9), glm::ivec3 (3, 6, 10), glm::ivec3 (3, 7, 11)
};

int graphByList[12][6] = {
	{ 1, 2, 4, 5, 8, 9 },
	{ 0, 3, 4, 5, 10, 11 },
	{ 0, 3, 6, 7, 8, 9 },
	{ 1, 2, 6, 7, 10, 11 },
	{ 0, 1, 5, 6, 8, 10 },
	{ 0, 1, 4, 7, 9, 11 },
	{ 2, 3, 4, 7, 8, 10 },
	{ 2, 3, 5, 6, 9, 11 },
	{ 0, 2, 4, 6, 9, 10 },
	{ 0, 2, 5, 7, 8, 11 },
	{ 2, 3, 4, 6, 8, 11 },
	{ 1, 3, 5, 7, 9, 10 }
};

glm::ivec4 edgesInFace[12] = {
	glm::ivec4 (0, 0, 4, 3), glm::ivec4 (0, 2, 5, 3), glm::ivec4 (1, 0, 4, 1), glm::ivec4 (1, 2, 5, 1),
	glm::ivec4 (0, 1, 2, 3), glm::ivec4 (0, 3, 3, 1), glm::ivec4 (1, 3, 2, 1), glm::ivec4 (1, 1, 3, 3),
	glm::ivec4 (2, 0, 4, 2), glm::ivec4 (3, 0, 4, 0), glm::ivec4 (2, 2, 5, 0), glm::ivec4 (3, 2, 5, 2)
};
glm::ivec4 faceEdgeIdx[6] = {
	glm::ivec4 (0, 4, 1, 5), glm::ivec4 (2, 7, 3, 6),
	glm::ivec4 (8, 6, 10, 4), glm::ivec4 (9, 5, 11, 7),
	glm::ivec4 (9, 2, 8, 0), glm::ivec4 (10, 3, 11, 1),
};

void genMarchingCubeTable ()
{

	for (int c = 0; c < 256; ++c) {
		std::vector< std::vector< glm::ivec2 > > edgeOnFaces (6);
		std::vector< glm::ivec2 > edges;

		for (int i = 0; i < 8; ++i) {
			if ((1 << i) & c) {
				glm::ivec3 edges = edgesOfVertexArr[i];
				for (int j = 0; j < 3; ++j) {
					int edge1 = edges[j];
					int edge2 = edges[(j + 1) % 3];
					if (edgesInFace[edge1].x == edgesInFace[edge2].x)
						edgeOnFaces[edgesInFace[edge1].x].push_back (glm::ivec2 (edgesInFace[edge1].y, edgesInFace[edge2].y));
					else if (edgesInFace[edge1].x == edgesInFace[edge2].z)
						edgeOnFaces[edgesInFace[edge1].x].push_back (glm::ivec2 (edgesInFace[edge1].y, edgesInFace[edge2].w));
					else if (edgesInFace[edge1].z == edgesInFace[edge2].x)
						edgeOnFaces[edgesInFace[edge1].z].push_back (glm::ivec2 (edgesInFace[edge1].w, edgesInFace[edge2].y));
					else
						edgeOnFaces[edgesInFace[edge1].z].push_back (glm::ivec2 (edgesInFace[edge1].w, edgesInFace[edge2].w));
				}
			}
		}
		for (int i = 0; i < 6; ++i) {
			while (edgeOnFaces[i].size () != 0) {
				glm::ivec2 edgePair = edgeOnFaces[i][0];
				edgeOnFaces[i].erase (edgeOnFaces[i].begin ());
				for (int j = 0; j < edgeOnFaces[i].size (); ++j) {
					bool isConnected = false;
					if (edgeOnFaces[i][j].x == edgePair.x) {
						edgePair.x = edgeOnFaces[i][j].y;
						isConnected = true;
					} else if (edgeOnFaces[i][j].x == edgePair.y) {
						edgePair.y = edgeOnFaces[i][j].y;
						isConnected = true;
					} else if (edgeOnFaces[i][j].y == edgePair.x) {
						edgePair.x = edgeOnFaces[i][j].x;
						isConnected = true;
					} else if (edgeOnFaces[i][j].y == edgePair.y) {
						edgePair.y = edgeOnFaces[i][j].x;
						isConnected = true;
					}
					if (isConnected) {
						edgeOnFaces[i].erase (edgeOnFaces[i].begin () + j);
						j = -1;
					}
				}
				edges.push_back (glm::ivec2 (faceEdgeIdx[i][edgePair.x], faceEdgeIdx[i][edgePair.y]));
			}
		}
		while (edges.size () != 0) {
			std::vector< int > poly;
			poly.push_back (edges[0].x);
			poly.push_back (edges[0].y);
			edges.erase (edges.begin ());
			for (int i = 0; i < edges.size (); ++i) {
				bool isConnected = false;
				if (edges[i].x == poly[poly.size () - 1]) {
					poly.push_back (edges[i].y);
					isConnected = true;
				} else if (edges[i].y == poly[poly.size () - 1]) {
					poly.push_back (edges[i].x);
					isConnected = true;
				}
				if (isConnected) {
					edges.erase (edges.begin () + i);
					i = -1;
				}
			}
			int v1 = poly[0];
			for (int i = 1; i < poly.size () - 2; ++i) {
				bool sameface = false;
				for (int j = 0; j < 6; ++j) {
					bool onface[3] = {false, false, false};
					for (int k = 0; k < 4; ++k)
						if (faceEdgeIdx[j][k] == v1)
							onface[0] = true;
						else if (faceEdgeIdx[j][k] == poly[i])
							onface[1] = true;
						else if (faceEdgeIdx[j][k] == poly[i + 1])
							onface[2] = true;
					if (onface[0] && onface[1] && onface[2])
						sameface = true;
				}
				if (!sameface)
					triangleList[c].push_back (glm::ivec3 (v1, poly[i], poly[i + 1]));
			}
		}
	}
	std::ofstream fOut ("TriangleTable.txt");
	fOut << "{\n";
	for (int i = 0; i < 256; ++i) {
		for (int j = 0; j < 5; ++j) {
			glm::ivec3 triangle;
			if (j < triangleList[i].size ())
				triangle = triangleList[i][j];
			else
				triangle = glm::ivec3 (-1, -1, -1);
			fOut << triangle.x << ", " << triangle.y << ", " << triangle.z;
			if (i != 255 || j != 4)
				fOut << ", ";
		}
		fOut << "\n";
	}
	fOut << "};";
}